package com.ifengxue.ioc.classloader;

import com.ifengxue.ioc.util.AnnotationUtil;
import com.ifengxue.ioc.util.ClassUtil;

import javax.annotation.Nonnull;
import java.io.File;
import java.lang.annotation.Annotation;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.*;

/**
 * 默认的{@link BeanClassLoader}实现，可以从<code>java.class.path</code>下扫描添加了指定注解类
 */
public class DefaultBeanClassLoader extends URLClassLoader implements BeanClassLoader {
    /** 关注的注解 */
    private Set<Class<? extends Annotation>> interestedAnnotations = new HashSet<>();
    /** 搜索的基础包 */
    private Set<String> basePackages = new HashSet<>();
    /** 加载class的路径 */
    private Set<URL> searchURLs = new HashSet<>();
    private String javaHome = System.getProperty("java.home");
    public DefaultBeanClassLoader(URL[] urls, ClassLoader parent) {
        super(urls, parent);
        Collections.addAll(searchURLs, urls);
        for (String classpath : System.getProperty("java.class.path").split(";")) {
            if (!classpath.startsWith(javaHome)) {
                searchURLs.add(createFileURL(classpath));
            }
        }
    }

    public DefaultBeanClassLoader(URL[] urls) {
        this(urls, getSystemClassLoader());
    }

    /**
     * 从<code>java.class.path</code>下扫描class
     */
    public DefaultBeanClassLoader() {
        this(new URL[] {});
    }

    @SafeVarargs
    @Override
    public final synchronized  <A extends Annotation> void addInterestedAnnotations(Class<A>... classes) {
        Collections.addAll(interestedAnnotations, classes);
    }

    @Override
    public synchronized void addBasePackages(String... packageNames) {
        Collections.addAll(basePackages, packageNames);
    }

    @Override
    public synchronized void addSearchPaths(String... paths) {
        try {
            for (String path : paths) {
                URL url = new URL(path);
                searchURLs.add(url);
                super.addURL(url);
            }
        } catch (MalformedURLException e) {
            throw new IllegalStateException("不合法的路径", e);
        }
    }

    @Nonnull
    @Override
    public Map<Class<? extends Annotation>, List<Class<?>>> findInterestedClasses() {
        List<Class<?>> classes = new LinkedList<>();
        //查找所有的类
        for (URL searchURL : searchURLs) {
            String path = searchURL.getPath();
            if (!path.endsWith(".jar")) {
                classes.addAll(ClassUtil.findClassesFromClasspath(searchURL, this));
            } else {
                classes.addAll(ClassUtil.findClassesFromJarPath(searchURL, this));
            }
        }
        Map<Class<? extends Annotation>, List<Class<?>>> map = new HashMap<>();
        for (Class<?> aClass : classes) {
            //过滤出在指定包下的类
            if (!isStartsWithBasePackage(aClass)) {
                continue;
            }
            for (Class<? extends Annotation> interestedAnnotation : interestedAnnotations) {
                //过滤出声明了指定注解的类
                if (AnnotationUtil.findNullableAnnotation(interestedAnnotation, aClass) == null) {
                    continue;
                }
                List<Class<?>> interestedAnnotationClassList = map.putIfAbsent(interestedAnnotation, new LinkedList<>());
                interestedAnnotationClassList = Optional.ofNullable(interestedAnnotationClassList).orElse(map.get(interestedAnnotation));
                interestedAnnotationClassList.add(aClass);
            }
        }
        return map;
    }

    /**
     * 检查指定的类是否在注册的包下
     * @param clazz
     * @return
     */
    private boolean isStartsWithBasePackage(Class<?> clazz) {
        for (String basePackage : basePackages) {
            if (clazz.getName().startsWith(basePackage)) {
                return true;
            }
        }
        return false;
    }

    @Nonnull
    private static URL createFileURL(@Nonnull String filePath) {
        try {
            return new URL(filePath);
        } catch (MalformedURLException e) {
            try {
                return new File(filePath).toURI().toURL();
            } catch (MalformedURLException e1) {
                throw new IllegalStateException(e1);
            }
        }
    }
}
